{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c3e2458f-d038-4845-93a0-d4ad830f9f90",
   "metadata": {},
   "source": [
    "# LangChain 核心模块学习：Chains\n",
    "\n",
    "对于简单的大模型应用，单独使用语言模型（LLMs）是可以的。\n",
    "\n",
    "**但更复杂的大模型应用需要将 `LLMs` 和 `Chat Models` 链接在一起 - 要么彼此链接，要么与其他组件链接。**\n",
    "\n",
    "LangChain 为这种“链式”应用程序提供了 `Chain` 接口。\n",
    "\n",
    "LangChain 以通用方式定义了 `Chain`，它是对组件进行调用序列的集合，其中可以包含其他链。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9cf0d43-107b-47ae-9e2c-2edaec38c800",
   "metadata": {},
   "source": [
    "## Chain Class 基类\n",
    "\n",
    "类继承关系：\n",
    "\n",
    "```\n",
    "Chain --> <name>Chain  # Examples: LLMChain, MapReduceChain, RouterChain\n",
    "```\n",
    "\n",
    "**代码实现：https://github.com/langchain-ai/langchain/blob/master/libs/langchain/langchain/chains/base.py**\n",
    "\n",
    "```python\n",
    "# 定义一个名为Chain的基础类\n",
    "class Chain(Serializable, Runnable[Dict[str, Any], Dict[str, Any]], ABC):\n",
    "    \"\"\"为创建结构化的组件调用序列的抽象基类。\n",
    "    \n",
    "    链应该用来编码对组件的一系列调用，如模型、文档检索器、其他链等，并为此序列提供一个简单的接口。\n",
    "    \n",
    "    Chain接口使创建应用程序变得容易，这些应用程序是：\n",
    "    - 有状态的：给任何Chain添加Memory可以使它具有状态，\n",
    "    - 可观察的：向Chain传递Callbacks来执行额外的功能，如记录，这在主要的组件调用序列之外，\n",
    "    - 可组合的：Chain API足够灵活，可以轻松地将Chains与其他组件结合起来，包括其他Chains。\n",
    "    \n",
    "    链公开的主要方法是：\n",
    "    - `__call__`：链是可以调用的。`__call__`方法是执行Chain的主要方式。它将输入作为一个字典接收，并返回一个字典输出。\n",
    "    - `run`：一个方便的方法，它以args/kwargs的形式接收输入，并将输出作为字符串或对象返回。这种方法只能用于一部分链，不能像`__call__`那样返回丰富的输出。\n",
    "    \"\"\"\n",
    "\n",
    "    # 调用链\n",
    "    def invoke(\n",
    "        self, input: Dict[str, Any], config: Optional[runnableConfig] = None\n",
    "    ) -> Dict[str, Any]:\n",
    "        \"\"\"传统调用方法。\"\"\"\n",
    "        return self(input, **(config or {}))\n",
    "\n",
    "    # 链的记忆，保存状态和变量\n",
    "    memory: Optional[BaseMemory] = None\n",
    "    \"\"\"可选的内存对象，默认为None。\n",
    "    内存是一个在每个链的开始和结束时被调用的类。在开始时，内存加载变量并在链中传递它们。在结束时，它保存任何返回的变量。\n",
    "    有许多不同类型的内存，请查看内存文档以获取完整的目录。\"\"\"\n",
    "\n",
    "    # 回调，可能用于链的某些操作或事件。\n",
    "    callbacks: Callbacks = Field(default=None, exclude=True)\n",
    "    \"\"\"可选的回调处理程序列表（或回调管理器）。默认为None。\n",
    "    在对链的调用的生命周期中，从on_chain_start开始，到on_chain_end或on_chain_error结束，都会调用回调处理程序。\n",
    "    每个自定义链可以选择调用额外的回调方法，详细信息请参见Callback文档。\"\"\"\n",
    "\n",
    "    # 是否详细输出模式\n",
    "    verbose: bool = Field(default_factory=_get_verbosity)\n",
    "    \"\"\"是否以详细模式运行。在详细模式下，一些中间日志将打印到控制台。默认值为`langchain.verbose`。\"\"\"\n",
    "\n",
    "    # 与链关联的标签\n",
    "    tags: Optional[List[str]] = None\n",
    "    \"\"\"与链关联的可选标签列表，默认为None。\n",
    "    这些标签将与对这个链的每次调用关联起来，并作为参数传递给在`callbacks`中定义的处理程序。\n",
    "    你可以使用这些来例如识别链的特定实例与其用例。\"\"\"\n",
    "\n",
    "    # 与链关联的元数据\n",
    "    metadata: Optional[Dict[str, Any]] = None\n",
    "    \"\"\"与链关联的可选元数据，默认为None。\n",
    "    这些元数据将与对这个链的每次调用关联起来，并作为参数传递给在`callbacks`中定义的处理程序。\n",
    "    你可以使用这些来例如识别链的特定实例与其用例。\"\"\"\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5d51fbb4-1d8e-4ec1-8c55-ec70247d4d64",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "c81a7df0-26c7-4eb8-92f1-cc54445cf507",
   "metadata": {},
   "source": [
    "## LLMChain\n",
    "\n",
    "LLMChain 是 LangChain 中最简单的链，作为其他复杂 Chains 和 Agents 的内部调用，被广泛应用。\n",
    "\n",
    "一个LLMChain由PromptTemplate和语言模型（LLM or Chat Model）组成。它使用直接传入（或 memory 提供）的 key-value 来规范化生成 Prompt Template（提示模板），并将生成的 prompt （格式化后的字符串）传递给大模型，并返回大模型输出。\n",
    "\n",
    "![](../images/llm_chain.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "757a67a6-c1aa-4dde-94ef-fb9865dc634c",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.llms import OpenAI\n",
    "from langchain.prompts import PromptTemplate\n",
    "\n",
    "llm = OpenAI(temperature=0.9, max_tokens=500)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "0b863511-ee01-43e8-8540-4e3f109a5a1a",
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = PromptTemplate(\n",
    "    input_variables=[\"product\"],\n",
    "    template=\"给制造{product}的有限公司取10个好名字，并给出完整的公司名称\",\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "b877560c-cb66-41ad-b484-b2df2a60a00d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "1. SkyForge Technologies Co.\n",
      "2. TridentTechCorp\n",
      "3. Supreme Nvidia Systems\n",
      "4. 4Tech Performance Solutions\n",
      "5. Atomix Graphics Designs\n",
      "6. Rendering Magicians LLC\n",
      "7. Neurathus Technologies\n",
      "8. InteliGraphix Inc.\n",
      "9. GPUForce Solutions\n",
      "10. RayCore Innovations\n"
     ]
    }
   ],
   "source": [
    "from langchain.chains import LLMChain\n",
    "\n",
    "chain = LLMChain(llm=llm, prompt=prompt)\n",
    "print(chain.run({\n",
    "    'product': \"性能卓越的GPU\"\n",
    "    }))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "727ccd76-0c6a-425b-bfc7-23d368c296f0",
   "metadata": {},
   "outputs": [],
   "source": [
    "chain.verbose = True"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "1766e01a-c5c4-4a74-9ebb-ecfc84101ba2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain.verbose"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "cfa71d7c-2859-47e1-9815-4be2ec9dbd74",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n",
      "Prompt after formatting:\n",
      "\u001b[32;1m\u001b[1;3m给制造性能卓越的GPU的有限公司取10个好名字，并给出完整的公司名称\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "\n",
      "\n",
      "1. 翼虎GPU有限公司\n",
      "2. 智高GPU有限公司\n",
      "3. 极芯GPU有限公司\n",
      "4. 飞龙GPU有限公司\n",
      "5. 华邦GPU有限公司\n",
      "6. 八爪龙GPU有限公司\n",
      "7. 宙斯GPU有限公司\n",
      "8. 虎鹰GPU有限公司\n",
      "9. 新欣GPU有限公司\n",
      "10.极酷GPU有限公司\n"
     ]
    }
   ],
   "source": [
    "print(chain.run({\n",
    "    'product': \"性能卓越的GPU\"\n",
    "    }))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac5411e7-b8ec-4c31-b659-deb44af038df",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "99cbf75e-98f4-4c99-b8a7-9a48cc28c7bc",
   "metadata": {},
   "source": [
    "## Sequential Chain\n",
    "\n",
    "串联式调用语言模型（将一个调用的输出作为另一个调用的输入）。\n",
    "\n",
    "顺序链（Sequential Chain ）允许用户连接多个链并将它们组合成执行特定场景的流水线（Pipeline）。有两种类型的顺序链：\n",
    "\n",
    "- SimpleSequentialChain：最简单形式的顺序链，每个步骤都具有单一输入/输出，并且一个步骤的输出是下一个步骤的输入。\n",
    "- SequentialChain：更通用形式的顺序链，允许多个输入/输出。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e192c8c-49fc-4d04-8444-e6aa6bd7b725",
   "metadata": {},
   "source": [
    "### 使用 SimpleSequentialChain 实现戏剧摘要和评论（单输入/单输出）\n",
    "\n",
    "![](../images/simple_sequential_chain_0.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a4d192a2-d563-4ab7-979f-640fa34f1914",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 这是一个 LLMChain，用于根据剧目的标题撰写简介。\n",
    "\n",
    "llm = OpenAI(temperature=0.7, max_tokens=1000)\n",
    "\n",
    "template = \"\"\"你是一位剧作家。根据戏剧的标题，你的任务是为该标题写一个简介。\n",
    "\n",
    "标题：{title}\n",
    "剧作家：以下是对上述戏剧的简介：\"\"\"\n",
    "\n",
    "prompt_template = PromptTemplate(input_variables=[\"title\"], template=template)\n",
    "synopsis_chain = LLMChain(llm=llm, prompt=prompt_template)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "3f7d429b-7ba7-4643-bd9f-fdb737ebf964",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 这是一个LLMChain，用于根据剧情简介撰写一篇戏剧评论。\n",
    "# llm = OpenAI(temperature=0.7, max_tokens=1000)\n",
    "template = \"\"\"你是《纽约时报》的戏剧评论家。根据剧情简介，你的工作是为该剧撰写一篇评论。\n",
    "\n",
    "剧情简介：\n",
    "{synopsis}\n",
    "\n",
    "以下是来自《纽约时报》戏剧评论家对上述剧目的评论：\"\"\"\n",
    "\n",
    "prompt_template = PromptTemplate(input_variables=[\"synopsis\"], template=template)\n",
    "review_chain = LLMChain(llm=llm, prompt=prompt_template)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5265129-5ccd-4e29-b221-0ec24eb84c2b",
   "metadata": {},
   "source": [
    "![](../images/simple_sequential_chain_1.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "de4d816e-16e1-4382-9064-6c03e5841ea2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 这是一个SimpleSequentialChain，按顺序运行这两个链\n",
    "from langchain.chains import SimpleSequentialChain\n",
    "\n",
    "overall_chain = SimpleSequentialChain(chains=[synopsis_chain, review_chain], verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "d503ac4f-e337-4436-86a1-7fd937efb06a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new SimpleSequentialChain chain...\u001b[0m\n",
      "\u001b[36;1m\u001b[1;3m\n",
      "\n",
      "这部戏剧讲述的是一个叫做米尔斯的少女，她被一群叫做三体人的外星人追赶，他们来自另一个星球，他们的目的是要毁灭地球。米尔斯决定与三体人作战，虽然她没有武器，却拥有一颗不屈不挠的心，最终她凭借勇气和智慧成功击败了三体人，从而拯救了地球的居民。本剧将带领观众走进一个充满惊险刺激的冒险故事，让每一位观众都能感受到战胜恐惧的力量。\u001b[0m\n",
      "\u001b[33;1m\u001b[1;3m\n",
      "\n",
      "《三体人和米尔斯》是一部令人惊叹的剧目，它将带领观众进入一个充满惊险刺激的冒险故事。这部剧叙述了米尔斯及其他地球居民与来自另一个星球的三体人的斗争，以拯救地球免于毁灭。尽管米尔斯没有武器，但她所展示出来的勇气和智慧令人敬佩。除了惊心动魄的剧情外，本剧还为观众展现了一个星际大战的另一面：每个人都有可能战胜恐惧，只要他们不断努力，就可以获得胜利。这部剧无疑是一部可观看的佳作，它将为观众带来愉悦的视觉享受和深思熟虑的思想洞察。\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "review = overall_chain.run(\"三体人不是无法战胜的\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ce4d75e1-8c57-4583-be7d-60a3488e35b5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new SimpleSequentialChain chain...\u001b[0m\n",
      "\u001b[36;1m\u001b[1;3m\n",
      "\n",
      "《星球大战第九季》描绘了一个激动人心的冒险故事，讲述了一群勇敢的英雄们如何在孤立无援的情况下，与强大而邪恶的军团战斗，以拯救他们星系的安危。我们的英雄们将面临巨大的挑战，被迫投身于一场未知的战斗中，必须凭借他们的勇气和勇敢的精神来应对任何情况。他们必须找到一种方式来拯救他们的星球免受邪恶势力的侵害，并证明自己是最优秀的英雄。\u001b[0m\n",
      "\u001b[33;1m\u001b[1;3m\n",
      "\n",
      "《星球大战第九季》是一部令人兴奋的冒险片，描绘了一群勇敢的英雄如何在孤立无援的情况下，抵抗强大而邪恶的军团，拯救他们星系的安危。这部电影的情节曲折而又有趣，让观众深入地了解英雄们的精神和行为，并带领他们走向一个成功的结局。在这部电影中，观众将看到英雄们面对着巨大的挑战，必须投身于一场未知的战斗中，体验到他们的勇气和勇敢的精神。《星球大战第九季》是一部精彩的剧目，值得每个人去观看。\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "review = overall_chain.run(\"星球大战第九季\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5fe32f1d-475d-4211-9b32-0c66dd8bff01",
   "metadata": {},
   "source": [
    "### 使用 SequentialChain 实现戏剧摘要和评论（多输入/多输出）\n",
    "\n",
    "![](../images/sequential_chain_0.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "2a04d84f-15c6-4a8d-a4db-200dfa405afa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# # 这是一个 LLMChain，根据剧名和设定的时代来撰写剧情简介。\n",
    "llm = OpenAI(temperature=.7, max_tokens=1000)\n",
    "template = \"\"\"你是一位剧作家。根据戏剧的标题和设定的时代，你的任务是为该标题写一个简介。\n",
    "\n",
    "标题：{title}\n",
    "时代：{era}\n",
    "剧作家：以下是对上述戏剧的简介：\"\"\"\n",
    "\n",
    "prompt_template = PromptTemplate(input_variables=[\"title\", \"era\"], template=template)\n",
    "# output_key\n",
    "synopsis_chain = LLMChain(llm=llm, prompt=prompt_template, output_key=\"synopsis\", verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "250afe66-e014-4097-9798-f9ba812023fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 这是一个LLMChain，用于根据剧情简介撰写一篇戏剧评论。\n",
    "\n",
    "template = \"\"\"你是《纽约时报》的戏剧评论家。根据该剧的剧情简介，你需要撰写一篇关于该剧的评论。\n",
    "\n",
    "剧情简介：\n",
    "{synopsis}\n",
    "\n",
    "来自《纽约时报》戏剧评论家对上述剧目的评价：\"\"\"\n",
    "\n",
    "prompt_template = PromptTemplate(input_variables=[\"synopsis\"], template=template)\n",
    "review_chain = LLMChain(llm=llm, prompt=prompt_template, output_key=\"review\", verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "9eb46f6d-841b-4b87-9ed5-a5913ef9aec5",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain.chains import SequentialChain\n",
    "\n",
    "m_overall_chain = SequentialChain(\n",
    "    chains=[synopsis_chain, review_chain],\n",
    "    input_variables=[\"era\", \"title\"],\n",
    "    # Here we return multiple variables\n",
    "    output_variables=[\"synopsis\", \"review\"],\n",
    "    verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "5a4a12ef-da2a-42ad-8044-fb71aedd3e2d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new SequentialChain chain...\u001b[0m\n",
      "\n",
      "\n",
      "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n",
      "Prompt after formatting:\n",
      "\u001b[32;1m\u001b[1;3m你是一位剧作家。根据戏剧的标题和设定的时代，你的任务是为该标题写一个简介。\n",
      "\n",
      "标题：三体人不是无法战胜的\n",
      "时代：二十一世纪的新中国\n",
      "剧作家：以下是对上述戏剧的简介：\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "\n",
      "\n",
      "\u001b[1m> Entering new LLMChain chain...\u001b[0m\n",
      "Prompt after formatting:\n",
      "\u001b[32;1m\u001b[1;3m你是《纽约时报》的戏剧评论家。根据该剧的剧情简介，你需要撰写一篇关于该剧的评论。\n",
      "\n",
      "剧情简介：\n",
      "\n",
      "\n",
      "《三体人不是无法战胜的》是一部有关在二十一世纪新中国的英雄故事。在一个被外星人侵略的世界中，一群普通人被迫必须与来自另一个世界的三体人搏斗，以保护他们的家园。虽然他们被认为是无法战胜的，但他们发现每个人都有能力成为英雄，并发挥他们的力量来保护自己的家园。他们向三体人发起激烈的攻击，最终将其击败。影片突出了勇气、信念和毅力，让观众看到了一个普通人如何成为英雄，拯救他们的家乡。\n",
      "\n",
      "来自《纽约时报》戏剧评论家对上述剧目的评价：\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'title': '三体人不是无法战胜的',\n",
       " 'era': '二十一世纪的新中国',\n",
       " 'synopsis': '\\n\\n《三体人不是无法战胜的》是一部有关在二十一世纪新中国的英雄故事。在一个被外星人侵略的世界中，一群普通人被迫必须与来自另一个世界的三体人搏斗，以保护他们的家园。虽然他们被认为是无法战胜的，但他们发现每个人都有能力成为英雄，并发挥他们的力量来保护自己的家园。他们向三体人发起激烈的攻击，最终将其击败。影片突出了勇气、信念和毅力，让观众看到了一个普通人如何成为英雄，拯救他们的家乡。',\n",
       " 'review': '\\n\\n《三体人不是无法战胜的》，一部讲述新中国英雄故事的影片，令人难以置信。影片中，一群普通人被迫面对外星人的侵略，但他们并不被看作不可战胜的，相反，他们的勇气、信念和毅力被突出展示，以完成救世的使命。影片给观众带来的是一种灵感，即每个人都有能力成为英雄，拯救他们的家乡。这部影片给中国电影带来了一丝新鲜感，并向观众展示了普通人可以发挥英雄力量的力量。'}"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "m_overall_chain({\"title\":\"三体人不是无法战胜的\", \"era\": \"二十一世纪的新中国\"})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8c20cf4e-25b4-453d-9f7a-84138ca25cf8",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "aaf8c391-9225-4e66-ad4d-d689b53a0379",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2fa40f2f-fcbe-4e69-b1c4-20f236033ae3",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "33b5061c-391e-4762-91c7-73b57f4ab501",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ef1db6e-3da4-4f9b-9707-0f30aa293dd7",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "8b6836f0-213d-4cac-abc9-3617831be3db",
   "metadata": {},
   "source": [
    "### Homework\n",
    "\n",
    "#### 使用 OutputParser 优化 overall_chain 输出格式，区分 synopsis_chain 和 review_chain 的结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1c7edb0a-675d-40c0-9f5d-d58f0170ce72",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
